/*
 * embed_lua.cpp 
 * Program that executes a function from lua plugin
 */
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <stdexcept>
#include <map>

#include "lua.hpp"

#include "extend_lua.h"


void compile(const std::string &file_name, const std::string &luac, Octets &chunk);
void read(const std::string &compiled_name, Octets &chunk);
void print_attributes(const Attributes &attributes);

int main(int argc, char **argv)
{
    if(argc != 4)
    {
        std::cerr<<"usage: "<<argv[0]<<" LUAC PLUGIN_NAME FUNCTION"<<std::endl;
        exit(1);
    }

    std::string luac(argv[1]);
    std::string file_name(argv[2]);
    std::string function(argv[3]);

    // generic data structure we will use to exchange information with Lua
    Attributes attributes;

    attributes.insert(std::make_pair("a", "a"));
    attributes.insert(std::make_pair("b", "b"));
    attributes.insert(std::make_pair("c", "c"));

    std::cout<<std::endl<<"initial state of attributes:"<<std::endl;
    print_attributes(attributes);

    // open Lua state and libs
    lua_State *lua = luaL_newstate();
    luaL_openlibs(lua);
    luaL_requiref (lua, "demo", luaopen_luademo, 1);
    lua_pop(lua, 1);

    // compile the plugin program
    Octets chunk;
    compile(file_name, luac, chunk);

    // load the plugin program
    int lua_result = luaL_loadbuffer(lua, (const char *)&chunk.at(0), chunk.size(), "extension");
    if(lua_result != 0)
    {
        std::string error = lua_tostring(lua, -1);
        lua_pop(lua, 1);
        throw std::runtime_error(error);
    }

    // execute global code; note that function definitions are statements
    lua_result = lua_pcall(lua, 0, LUA_MULTRET, 0);
    if(lua_result != 0)
    {
        std::string error = lua_tostring(lua, -1);
        lua_pop(lua, 1);
        throw std::runtime_error(error);
    }

    // prepare the global function for execution
    lua_getglobal(lua, function.c_str());
    if(!lua_isfunction(lua, -1))
    {
        std::string error = "function " + function + " not found";
        lua_pop(lua, 1);
        throw std::runtime_error(error);
    }

    // push arguments; the order is the same as parameters in function definition
    lua_pushstring(lua, "this will be the first parameter");
    lua_pushlightuserdata(lua, &attributes);

    // call the function
    // pass 2 arguments, return 2 results
    if(lua_pcall(lua, 2, 2, 0) != 0)
    {
        std::string error = lua_tostring(lua, -1);
        lua_pop(lua, 1);
        throw std::runtime_error(error);
    }

    // get results.
    // first result is before the second
    bool result = lua_toboolean(lua, -2);
    std::string error_message = lua_tostring(lua, -1);
    lua_pop(lua, 2);

    lua_close(lua);
    std::cout<<"Lua returned: "<<result<<", "<<error_message<<std::endl<<std::endl;

    std::cout<<"final state of attributes:"<<std::endl;
    print_attributes(attributes);

    return 0;
}

//
// compile the plugin and return it as a buffer
//
void compile(const std::string &file_name, const std::string &luac, Octets &chunk)
{
    std::string compiled_name(file_name);
    compiled_name.insert(compiled_name.size(), "c");

    std::string cmd;
    cmd.reserve(1024);
    cmd.insert(cmd.size(), luac);
    cmd.insert(cmd.size(), " -o ");
    cmd.insert(cmd.size(), compiled_name);
    cmd.insert(cmd.size(), " ");
    cmd.insert(cmd.size(), file_name);

    int res = system(cmd.c_str());
    if(res != -1 && WEXITSTATUS(res) != 0)
    {
        throw std::runtime_error(strerror(errno));
    }

    read(compiled_name, chunk);
}

//
// read compiled plugin
//
void read(const std::string &compiled_name, Octets &chunk)
{
    std::ifstream file(compiled_name.c_str(), std::ios::in|std::ios::binary|std::ios::ate);
    if (file.is_open())
    {
        std::ifstream::pos_type size;
        size = file.tellg();
        chunk = Octets(size, 0);
        file.seekg (0, std::ios::beg);
        file.read ((char *)&chunk.at(0), size);
        file.close();
    }
    else
    {
        throw std::runtime_error("error opening plugin file");
    }
}


void print_attributes(const Attributes &attributes)
{
    for(Attributes::const_iterator i = attributes.begin(); i != attributes.end(); ++i)
    {
        std::cout<<i->first<<"->"<<i->second<<std::endl; 
    }
    std::cout<<std::endl;
}

